package com.pisolution.actuator.client.registration;

import com.pisolution.actuator.client.ActuatorClientConfig;

import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 用于注册客户端应用至监控中心.
 *
 * @author sora
 * @version 0.1.0
 */
public class ApplicationRegister {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationRegister.class);

    // -------------------------------------------------------------------------------------------------

    /** 用于存储监控中心反馈的应用唯一标识. */
    private final AtomicReference<String> applicationId;

    /** 用于获取注册中心地址. */
    private final ActuatorClientConfig cfg;

    /** 用于与监控中心进行请求交互. */
    private final RestTemplate tpl;

    /** 用于获取客户端应用信息. */
    private final ApplicationFactory factory;

    /** 用于不间断上传心跳信息至监控中心. */
    private final ScheduledExecutorService service;

    // -------------------------------------------------------------------------------------------------

    /** 用于取消心跳上传. */
    private ScheduledFuture<?> future;

    // -------------------------------------------------------------------------------------------------
    //  初始化
    // -------------------------------------------------------------------------------------------------

    /**
     * 初始化 {@link ApplicationRegister}.
     *
     * @param cfg 用于获取注册中心地址.
     * @param tpl 用于与监控中心进行请求交互.
     * @param factory 用于获取客户端应用信息.
     */
    public ApplicationRegister(final ActuatorClientConfig cfg, final RestTemplate tpl, final ApplicationFactory factory) {
        this.cfg = cfg;
        this.tpl = tpl;
        this.factory = factory;
        this.service = Executors.newSingleThreadScheduledExecutor();

        this.applicationId = new AtomicReference<>();
    }

    // -------------------------------------------------------------------------------------------------
    //  应用注册
    // -------------------------------------------------------------------------------------------------

    /**
     * 尝试向监控中心进行注册.
     */
    public void register() {
        // 1. 获取应用信息.
        final Application application = this.factory.create();

        // 2. 获取注册重试次数上限.
        int retry = this.cfg.getRetry();

        // 3. 尝试提交注册申请.
        while (retry-- > 0) {
            try {
                // 4. 向监控中心发送注册申请.
                final ResponseEntity<Map<String, String>> response = this.tpl.exchange(
                    String.join("/", this.cfg.getUrl(), "pi-actuator-service", "register"),
                    HttpMethod.POST,
                    new HttpEntity<>(application, this.headers()),
                    new ParameterizedTypeReference<Map<String, String>>() {}
                );

                if (response.getBody() == null) {
                    LOGGER.debug("The actuator server seems that something worst happened.");

                    continue;
                }

                final String id = Preconditions.checkNotNull(response.getBody()).getOrDefault("id", null);
                if (this.applicationId.compareAndSet(null, id)) {
                    LOGGER.info("The application succeeds to register itself to the actuator server with id {}.", id);
                }

                // 5. 开启心跳上传.
                this.future = this.service.scheduleAtFixedRate(
                    this::heartbeat,
                    this.cfg.getDelay(),
                    this.cfg.getPeriod(),
                    TimeUnit.MILLISECONDS
                );

                return;
            } catch (Exception e) {
                LOGGER.debug("The application failed to register itself to the actuator server with {} times left.", retry, e);
            }
        }

        LOGGER.warn("The application failed to register itself to the actuator server.");
    }

    /**
     * 尝试取消注册自身至监控中心.
     */
    public void deregister() {
        // 1. 如果尚未注册成功 则无需进行取消注册处理.
        if (StringUtils.isBlank(this.applicationId.get())) {
            return;
        }

        // 2. 关闭心跳上传.
        if (this.future != null) {
            this.future.cancel(true);
        }

        // 3. 关闭心跳上传服务.
        if (!this.service.isShutdown()) {
            this.service.shutdown();
        }

        this.tpl.delete(String.join(
            "/",
            this.cfg.getUrl(),
            "pi-actuator-service",
            "register",
            this.applicationId.get()
        ));
    }

    /**
     * 获取请求头配置.
     *
     * @return 请求头配置信息.
     */
    private HttpHeaders headers() {
        final HttpHeaders headers = new HttpHeaders();

        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

        return HttpHeaders.readOnlyHttpHeaders(headers);
    }

    // -------------------------------------------------------------------------------------------------
    //  心跳上传
    // -------------------------------------------------------------------------------------------------

    /**
     * 上传自身心跳信息至监控中心以维持监控中心中相应服务对象的存活状态.
     */
    private void heartbeat() {
        // 1. 发送心跳数据.
        final ResponseEntity<Map<String, String>> response = this.tpl.exchange(
            String.join("/", this.cfg.getUrl(), "pi-actuator-service", "heartbeat"),
            HttpMethod.POST,
            new HttpEntity<>(Collections.singletonMap("applicationId", this.applicationId.get()), this.headers()),
            new ParameterizedTypeReference<Map<String, String>>() {}
        );

        // 2. 处理监控中心反馈.
        if (response.getStatusCode().is2xxSuccessful()) {
            LOGGER.debug("Succeed to upload the heartbeat to the actuator server.");

            return;
        }

        LOGGER.warn("Could not upload the heartbeat to the actuator server.");
    }
}
